﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace comm
{
    public class TcpClient
    {
        public static void Connect(string ip, int port, Action<bool, TcpClient> onConnect)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.BeginConnect(new IPEndPoint(IPAddress.Parse(ip), port), new AsyncCallback(result =>
            {
                try
                {
                    socket.EndConnect(result);
                    var client = new TcpClient(socket, "", olclient => { });
                    client.BeginReceive();
                    onConnect(true, client);
                }
                catch (Exception)
                {
                    onConnect(false, null);
                }
            }), null);
        }

        const int _bufferMax = 1024;
        List<byte> _bagBuffer = new List<byte>();
        byte[] _buffer = new byte[_bufferMax];
        Socket _socket = null;
        string _clientId = null;
        Action<string> _removeClientCallback = null;

        public Action<TcpClient, byte[]> OnReceiveCallback = null;
        public Action<TcpClient> OnCloseCallback = null;

        public string ClientId { get { return _clientId; } }
        public string IP { get; private set; }
        public int Port { get; private set; }
        public bool Connected
        {
            get
            {
                return _socket == null ? false : _socket.Connected;
            }
        }

        public TcpClient(Socket socket, string clientId, Action<string> removeClientCallback)
        {
            _clientId = clientId;
            _removeClientCallback = removeClientCallback;

            var remotePoint = (IPEndPoint)socket.RemoteEndPoint;
            IP = remotePoint.Address.ToString();
            Port = remotePoint.Port;

            _socket = socket;
        }

        public void BeginReceive()
        {
            try
            {
                _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(EndReceive), null);
            }
            catch (Exception ex)
            {
                Close("new Client() err " + ex.Message);
            }
        }

        byte[][] Decode(byte[] value, int index, int length)
        {
            var list = new List<byte[]>();
            byte b;
            for (int i = index; i < length; i++)
            {
                b = value[i];
                if (b == 0)
                {
                    list.Add(Convert.FromBase64String(Encoding.UTF8.GetString(_bagBuffer.ToArray(), 0, _bagBuffer.Count)));
                    _bagBuffer.Clear();
                }
                else
                {
                    _bagBuffer.Add(b);
                }
            }
            return list.ToArray();
        }
        byte[] Encode(byte[] value)
        {
            var bytes = Encoding.UTF8.GetBytes(Convert.ToBase64String(value));
            var list = new List<byte>(bytes);
            list.Add(0);
            return list.ToArray();
        }
        void EndReceive(IAsyncResult result)
        {
            try
            {
                var count = _socket.EndReceive(result);
                if (count == 0)
                {
                    Close("EndReceive count=0");
                    return;
                }
                var bytesList = Decode(_buffer, 0, count);
                if (OnReceiveCallback != null && bytesList.Length > 0)
                {
                    foreach (var bytes in bytesList)
                    {
                        OnReceiveCallback(this, bytes);
                    }
                }
                _socket.BeginReceive(_buffer, 0, _bufferMax, SocketFlags.None, new AsyncCallback(EndReceive), null);
            }
            catch (Exception ex)
            {
                Close("EndReceive() err " + ex.Message + "\r\n" + ex.StackTrace);
            }
        }
        void EndSend(IAsyncResult result)
        {
            try
            {
                _socket.EndSend(result);
            }
            catch (Exception ex)
            {
                Close("EndSend() err " + ex.Message);
            }
        }
        void Close(string msg)
        {
            //Console.WriteLine(msg);
            //if (Connected)
            //{
            try
            {
                _socket.Close();
            }
            catch (Exception)
            {
            }
            if (OnCloseCallback != null)
            {
                OnCloseCallback(this);
            }
            _removeClientCallback(ClientId);
            //}
        }

        public int Send(byte[] data)
        {
            if (Connected)
            {
                try
                {
                    return _socket.Send(Encode(data));
                }
                catch (Exception ex)
                {
                    Close("call Send() err " + ex.Message);
                }
            }
            return 0;
        }
        public int SendAsync(byte[] data)
        {
            if (Connected)
            {
                try
                {
                    var bytes = Encode(data);
                    _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(EndSend), null);
                    return bytes.Length;
                }
                catch (Exception ex)
                {
                    Close("call SendAsync() err " + ex.Message);
                }
            }
            return 0;
        }
        public void Close()
        {
            Close("call CloseConnect");
        }
    }
}
